package indi.zhifa.recipe.bailan.framework.auth.filter;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import indi.zhifa.recipe.bailan.framework.auth.entity.dto.TokenAuthNodeDto;
import indi.zhifa.recipe.bailan.framework.auth.entity.dto.auth.TableAuthColumn;
import indi.zhifa.recipe.bailan.framework.auth.entity.dto.auth.TableAuthMapItem;
import indi.zhifa.recipe.bailan.framework.auth.entity.annotations.auth.DataPermission;
import indi.zhifa.recipe.bailan.framework.auth.entity.BaseTokenObject;
import indi.zhifa.recipe.bailan.framework.auth.entity.dto.datapermission.DataPermissionData;
import indi.zhifa.recipe.bailan.framework.auth.entity.enums.EDataPermissionType;
import indi.zhifa.recipe.bailan.framework.auth.memo.MapperAuthMapMemo;
import indi.zhifa.recipe.bailan.framework.auth.util.IBaseTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.schema.Column;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Order(value = 101)
@Aspect
@Slf4j
@RequiredArgsConstructor
@Component
public class DataPermissionAop implements DataPermissionHandler {

    private final IBaseTokenUtil mTokenUtil;
    private final MapperAuthMapMemo mMapperAuthMapMemo;
    private ThreadLocal<DataPermissionData> dataPermissionDataThreadLocal = new ThreadLocal<>();

    @Before("@annotation(permission)")
    public void doBefore(JoinPoint point, DataPermission permission) throws Throwable {
        if (!mTokenUtil.hasTokenObject()) {
            return;
        }
        BaseTokenObject baseTokenObject = mTokenUtil.getTokenObject();
        Set<String> roleCodes = baseTokenObject.getRoles().stream().collect(Collectors.toSet());
        String[] exemptionRoles = permission.exemptionRoles();
        DataPermissionData dataPermissionData = new DataPermissionData();
        dataPermissionData.setUserId(baseTokenObject.getId());
        dataPermissionData.setDepInfo(baseTokenObject.getAuthNodeInfo());
        dataPermissionData.setExemption(false);
        dataPermissionData.setDataPermission(permission);
        for(String exemptionRole : exemptionRoles){
            if(roleCodes.contains(exemptionRole)){
                dataPermissionData.setExemption(true);
            }
        }
        dataPermissionDataThreadLocal.set(dataPermissionData);
    }

    @After("@annotation(permission)")
    public void doAfter(JoinPoint point,DataPermission permission) {
        dataPermissionDataThreadLocal.remove();
    }

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        DataPermissionData dataPermissionData = dataPermissionDataThreadLocal.get();
        if (null == dataPermissionData) {
            return where;
        }
        if(dataPermissionData.isExemption()){
            return where;
        }
        TableAuthMapItem tableAuthMapItem = mMapperAuthMapMemo.loadClass(mappedStatementId);
        if(!tableAuthMapItem.isExist()){
            return where;
        }

        DataPermission dataPermission = dataPermissionData.getDataPermission();
        EDataPermissionType dataPermissionType = dataPermission.permissionType();
        String domain = dataPermission.domain();
        if(EDataPermissionType.ID_EQ == dataPermissionType){
            String relUserIdColumnName = tableAuthMapItem.getRelUserColumnName();
            if(!StringUtils.hasText(relUserIdColumnName)){
                return where;
            }
            Column relUserIdColumn = new Column(relUserIdColumnName);
            LongValue userId = new LongValue(dataPermissionData.getUserId());
            EqualsTo equalsTo = new EqualsTo(relUserIdColumn, userId);
            return new AndExpression(where,equalsTo);
        }else if(EDataPermissionType.AUTH_ID == dataPermissionType){
            if(tableAuthMapItem.isError()){
                return where;
            }
            Map<String, TableAuthColumn> tableAuthColumnsMap = tableAuthMapItem.getTableAuthColumnsMap();
            Map<String, TokenAuthNodeDto> tokenDepartmentDtoMap = dataPermissionData.getDepInfo();
            TableAuthColumn tableAuthColumn = tableAuthColumnsMap.get(domain);
            if(null == tableAuthColumn){
                return where;
            }
            TokenAuthNodeDto tokenAuthNodeDto = tokenDepartmentDtoMap.get(domain);
            if(null == tokenAuthNodeDto){
                return where;
            }
            String depIdColumnName = tableAuthColumn.getIdColumn();
            if(!StringUtils.hasText(depIdColumnName)){
                return where;
            }
            Column depIdColumn = new Column(depIdColumnName);
            LongValue depId = new LongValue(tokenAuthNodeDto.getAuthId());
            EqualsTo equalsTo = new EqualsTo(depIdColumn, depId);
            return new AndExpression(where,equalsTo);
        }else if(EDataPermissionType.AUTH_CASCADE == dataPermissionType){
            if(tableAuthMapItem.isError()){
                return where;
            }
            Map<String, TableAuthColumn> tableAuthColumnsMap = tableAuthMapItem.getTableAuthColumnsMap();
            Map<String, TokenAuthNodeDto> tokenDepartmentDtoMap = dataPermissionData.getDepInfo();
            TableAuthColumn tableAuthColumn = tableAuthColumnsMap.get(domain);
            if(null == tableAuthColumn){
                return where;
            }
            TokenAuthNodeDto tokenAuthNodeDto = tokenDepartmentDtoMap.get(domain);
            if(null == tokenAuthNodeDto){
                return where;
            }
            String depCascadeColumnName = tableAuthColumn.getCascadeColumn();
            if(!StringUtils.hasText(depCascadeColumnName)){
                return where;
            }
            Column depCascadeColumn = new Column(depCascadeColumnName);
            StringValue depCascade = new StringValue(tokenAuthNodeDto.getAuthCascade()+"%");
            LikeExpression likeExpression = new LikeExpression();
            likeExpression.setLeftExpression(depCascadeColumn);
            likeExpression.setRightExpression(depCascade);
            return new AndExpression(where,likeExpression);
        }else if(EDataPermissionType.AUTH_CASCADE_CHILDREN == dataPermissionType){
            if(tableAuthMapItem.isError()){
                return where;
            }
            Map<String, TableAuthColumn> tableAuthColumnsMap = tableAuthMapItem.getTableAuthColumnsMap();
            Map<String, TokenAuthNodeDto> tokenDepartmentDtoMap = dataPermissionData.getDepInfo();
            TableAuthColumn tableAuthColumn = tableAuthColumnsMap.get(domain);
            if(null == tableAuthColumn){
                return where;
            }
            TokenAuthNodeDto tokenAuthNodeDto = tokenDepartmentDtoMap.get(domain);
            if(null == tokenAuthNodeDto){
                return where;
            }
            String depCascadeColumnName = tableAuthColumn.getCascadeColumn();
            if(!StringUtils.hasText(depCascadeColumnName)){
                return where;
            }
            Column depCascadeColumn = new Column(depCascadeColumnName);
            StringValue depCascade = new StringValue(tokenAuthNodeDto.getAuthCascade()+"-%");
            LikeExpression likeExpression = new LikeExpression();
            likeExpression.setLeftExpression(depCascadeColumn);
            likeExpression.setRightExpression(depCascade);
            return new AndExpression(where,likeExpression);
        }

        return where;
    }

}
